1. Technical Field
The disclosed technology relates to optimizing computer memory usage.
2. Background Art
One skilled in the art will understand that in many Object-Oriented-Programming (OOP) environments, the root class of a class hierarchy is the foundation class of the class hierarchy and has no superclasses. The root class can be extended by a subclass such that the root class is the superclass of the subclass. The subclass inherits the properties and methods of its superclass. A subclass can itself be extended and serve as a superclass for its own subclasses. A subclass can extend the capability of its superclass. In some programming languages, a subclass can inherit from multiple superclasses while in other programming languages the subclass can only inherit from a single superclass. The root class is conventionally referred to as being at the top of the class hierarchy. Each descendent of the root class extends the definition of its superclass by a class-extension. Thus, for Class C that is a subclass of Class B, which is a subclass of root-Class A, the class definition for Class A has two class-extensions; one to extend Class A to Class B and one to extend Class B to Class C.
An object is instantiated from the object's class. Thus, if the object's class is not the root class the object will be instantiated from a subclass of the root class. When the object is instantiated, sufficient memory is allocated to store the reference fields (for example, class/object variables) defined by the object's class including its superclasses. Execution of the methods defined in the object's class hierarchy can allocate memory from a dynamic memory area (for example, the heap) and cause a reference to the allocated memory to be stored in a reference field within the object's class hierarchy. The memory used to instantiate an object can be from the same or different dynamic memory area.
An executing program generally instantiates many objects. The relationship between the objects can be represented by an object graph. Allocated memory that can not be reached through a reference in the object graph is unreachable.
Many programs, in particular those written using the object-oriented paradigm, dynamically allocate, use, and reclaim memory during execution. Programmers using a procedural programming paradigm or hybrid programming paradigm (such as C++) require explicit programming to reclaim memory allocated from the dynamic memory area that is no longer used. Correctly determining when memory is not in use is a well-known and difficult aspect of programming. Incorrectly retaining unused memory results in “memory leakage” such that the memory available in the dynamic memory area decreases over time as the dynamic memory area becomes polluted with no-longer-used, but not reclaimed memory.
Modern computer languages are designed with a garbage collection capability that automatically detects and reclaims allocated but unreachable memory. This capability relieves the programmer of the memory management burden and removes the possibility of “memory leakage”. Garbage collection techniques are well known in the art and, for example, discover allocated memory that is unreachable by any strong reference in the object graph.
Some unreachable objects may need to perform clean-up operations prior to reclamation of the memory allocated to, or memory referenced by that object (its related memory). For example, if the object has allocated a system resource such as a file, socket, user interface element, etc. prior to becoming unreachable the information about that resource is contained in the object's related memory. To support this capability, many OOP environments include a finalization logic that can invoke the object's finalize-method prior to reclaiming the object's related memory. The finalize-method can then perform any desired clean-up operations to release the system resource using the object's related memory. The object's related memory can only be reclaimed if that memory can only be reached through that object (that is, if the memory is related to two objects, one of which is reachable from the object graph, then that memory is not solely reachable from the other object and cannot be reclaimed).
In addition, in some programming environments (such JAVA), the finalize-method can reattach the object to the object graph such that the object and its related memory are again reachable from the object graph.
The runtime environment generally recovers allocated but unreachable memory by the garbage collection system discovering such memory as a discovered reference and queuing the discovered reference onto a finalization queue (or by adding it to a finalization set) as a finalizable-object. After adding the discovered reference to the finalization set objects in the discovered reference are finalizer-reachable in that the discovered reference is reachable from the finalization logic but is still unreachable from other portions of the object graph. The finalization logic (after some indeterminate but potentially significantly long period of time) can invoke the finalize-method of the finalizable-object. The finalize-method then can access the object's related memory and release system resources or perform other programmed operations prior to the memory containing the object (and other related memory that is solely referenced by the object) being reclaimed. The invocation of the finalize-method by the finalization logic changes the state of the object from finalizable to finalized. After the finalize-method completes, the finalization logic performs whatever steps are needed to reclaim, or enable reclamation of the finalized object and its related memory.
One embodiment of the technology described above is used in the JAVA program environment (See section 12.6.1 of the JAVA Language Specification 3rd ed.). In this embodiment every object can be characterized by two attributes: the object may be reachable, finalizer-reachable, or unreachable, and the object may also be unfinalized, finalizable or finalized. While JAVA-related terminology is used herein to help describe the technology, the claims are not limited to JAVA and extend to any technology that has problems equivalent to those described.
One skilled in the art will understand that an object is created by instantiating a class definition. The class definition can include a finalize-method. Such a one will also understand the difference between a trivial finalize-method and a non-trivial finalize-method. That is, class hierarchies used to instantiate objects often include a trivial finalize-method at the root of the class hierarchy. This trivial finalize-method can be overridden by a non-trivial finalize-method defined by a subclass lower in the class hierarchy. Some finalization logic implementations may invoke all finalize-methods including trivial finalize-methods; other implementations are optimized to only invoke non-trivial finalize-methods.
One of the problems with prior-art finalization is that once an object is added to the finalization set (as a finalizable-object), the finalizable-object's related memory as well as other objects and their related memory that are reachable through the finalizable-object must be retained until the finalizable-object's finalize-method is invoked and the finalizable-object becomes finalized. This problem occurs even if the finalizable-object's finalize-method will not access a related memory and even if that related memory is solely reachable through the finalizable-object. This situation can drastically reduce the amount of available memory in the dynamic memory area. One common example is when a programmer subclasses a library class without recognizing that the library class includes a non-trivial finalize-method.
TABLE 1class A {  Object x;}class B extends A {  Object y;  public void finalize( ) {    dispose( );  }  public void dispose( ) {    “use y”;  }}class C extends B {  Object z;}
Table 1 represents code written in the Java™ programming language that demonstrates the above problem. Assume that object-c is instantiated from Class C. Class C reserves space (a reference field) for object-z, but because Class B extends Class A and Class C extends Class B, the instantiation of object-c also reserves space for object-x and object-y. Sometime after object-c becomes unreachable, the garbage collection system will discover object-c and add it to the finalization set (making object-c a finalizable-object). This means that all of object-c's related memory—memory used for and/or referenced by object-x, object-y and object-z—will be retained even though object-z is not needed by the finalize-method. This retention of memory that will not be accessed by the finalize-method restricts the amount of dynamic memory available to the program.
To assist in the discussion of the technology, the portion of Class B that is not part of Class A is termed “a class-extension”. Thus, assuming the Class A is the root class, Class C consists of the root class and two class-extensions.
The inventors have previously realized that these problems can be overcome in JAVA by bypassing JAVA's finalization logic coupled with complex class design the use of weak references. This technique is explained in “How to Handle Java Finalization's Memory-Retention Issues” published Dec. 27, 2005, by Tony Printezis. This paper is included with the Information Disclosure filed herewith or prior to the first office action. The inventors are not aware of any finalization technology that addresses the above problems.
It would be advantageous to provide a programming environment that does not retain objects, memory, and/or resources that are not used by the finalize method of a superclass.